package stc.skymobi.netty.util;

import com.google.common.primitives.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import stc.skymobi.bean.bytebean.core.BeanFieldCodec;
import stc.skymobi.bean.tlv.TLVSignal;
import stc.skymobi.bean.tlv.annotation.TLVAttribute;
import stc.skymobi.bean.tlv.encode.TLVEncodeContext;
import stc.skymobi.bean.tlv.encode.TLVEncoderOfBean;
import stc.skymobi.bean.xip.annotation.SaipSignalAnnotation;
import stc.skymobi.bean.xip.annotation.SsipSignal;
import stc.skymobi.bean.xip.core.SaipSignal;
import stc.skymobi.bean.xip.core.XipSignal;
import stc.skymobi.transport.protocol.xip.XipHeader;
import stc.skymobi.util.ByteUtils;

import java.util.List;
import java.util.UUID;

public class TlvEncoder {
    private static final Logger logger = LoggerFactory.getLogger(TlvEncoder.class);

    private int dumpBytes = 256;

    private BeanFieldCodec xipBeanCodec;
    private TLVEncoderOfBean tlvBeanEncoder;

    public byte[] encode(Object msg) throws Exception {
        if (msg instanceof XipSignal) {
            SaipSignalAnnotation attr = msg.getClass().getAnnotation(SaipSignalAnnotation.class);
            if (attr != null) {
                return encodeSaip((SaipSignal) msg, attr);
            }
            SsipSignal signal = msg.getClass().getAnnotation(SsipSignal.class);
            if (signal != null) {
                return encodeSsip((XipSignal) msg, signal);
            }
            throw new RuntimeException("invalid saip/ssip signal, bcs of no messageCode.");
        } else if (msg instanceof TLVSignal) {
            return encodeTLV((TLVSignal) msg);
        } else {
            throw new RuntimeException("encode: bean " + msg
                    + " is not neigher XipSignal nor TLVSignal.");
        }
    }

    protected byte[] encodeSaip(SaipSignal signal, SaipSignalAnnotation attr) throws Exception {
        byte[] bytesBody =
                xipBeanCodec.encode(
                        xipBeanCodec.getEncContextFactory().createEncContext(signal, signal.getClass(), null));

        XipHeader header = createHeader((byte) 1, signal.getIdentification(), attr.messageCode(), null == bytesBody ? 0 : bytesBody.length, XipHeader.SAIP_HEADER_LENGTH);

        header.setTypeForClass(signal.getClass());
        header.setSrcModule(signal.getSrcModule());
        header.setDstModule(signal.getDstModule());

        byte[] bytes = Bytes.concat(
                xipBeanCodec.encode(
                        xipBeanCodec.getEncContextFactory().createEncContext(
                                header, XipHeader.class, null)
                ),
                bytesBody
        );

        if (logger.isDebugEnabled()) {
            logger.debug("encode XipMessage:" + signal);
            logger.debug("and XipMessage raw bytes -->");
            logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
        }

        return bytes;
    }

    private byte[] encodeSsip(XipSignal signal, SsipSignal attr) throws Exception {
        //	once
        byte[] bytesBody =
                xipBeanCodec.encode(
                        xipBeanCodec.getEncContextFactory().createEncContext(signal, signal.getClass(), null));

        XipHeader header = createHeader((byte) 1, signal.getIdentification(), attr.messageCode(), bytesBody.length, XipHeader.SSIP_HEADER_LENGTH);

        header.setTypeForClass(signal.getClass());

        byte[] bytes = Bytes.concat(
                xipBeanCodec.encode(
                        xipBeanCodec.getEncContextFactory().createEncContext(
                                header, XipHeader.class, null)
                ),
                bytesBody
        );

        if (logger.isDebugEnabled()) {
            logger.debug("encode XipMessage:" + signal);
            logger.debug("and XipMessage raw bytes -->");
            logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
        }

        return bytes;
    }

    private byte[] encodeTLV(TLVSignal signal) throws Exception {
        TLVEncodeContext ctx =
                tlvBeanEncoder.getEncodeContextFactory().createEncodeContext(signal.getClass(), null);
        List<byte[]> byteList = tlvBeanEncoder.encode(signal, ctx);

        TLVAttribute attr = signal.getClass().getAnnotation(TLVAttribute.class);
        if (null == attr) {
            throw new RuntimeException("invalid top tlv object, missing @TLVAttribute.");
        }

        final byte[] bytesBody = ByteUtils.union(byteList);
        final int headerSize = xipBeanCodec.getStaticByteSize(XipHeader.class);
        final XipHeader header = createHeader((byte) 2, signal.getIdentification(),
                attr.tag(), bytesBody.length, headerSize);//Tlv的basicVer=2，头类型可以为ssip，也可以为saip，因此头长度必须计算出来

        header.setSourceId(signal.getSourceId());
        header.setTypeForClass(signal.getClass());

        final byte[] bytes = Bytes.concat(
                xipBeanCodec.encode(
                        xipBeanCodec.getEncContextFactory().createEncContext(
                                header, XipHeader.class, null)
                ),
                bytesBody
        );

        if (logger.isDebugEnabled()) {
            logger.debug("encode TLV: {} with headerSize {}", signal, headerSize);
            logger.debug("and TLV raw bytes -->");
            logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
        }

        return bytes;
    }

    private XipHeader createHeader(byte basicVer, UUID id, int messageCode, int messageLen, int headerSize) {
        final XipHeader header = new XipHeader();
        header.setTransaction(id);
        header.setLength(headerSize + messageLen);
        header.setMessageLength(messageLen);
        header.setMessageCode(messageCode);
        header.setBasicVer(basicVer);

        return header;
    }

    public byte[] genHeaderToBytes(byte basicVer, UUID id, int messageCode, int messageLen) {

        //	指定 basicVer 为 != 1 != 2 的其它版本
        final XipHeader header = createHeader(basicVer, id, messageCode, messageLen,
                xipBeanCodec.getStaticByteSize(XipHeader.class));

        return xipBeanCodec.encode(
                xipBeanCodec.getEncContextFactory().createEncContext(
                        header, XipHeader.class, null)
        );

    }

    public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
        this.xipBeanCodec = xipBeanCodec;
    }

    public int getDumpBytes() {
        return dumpBytes;
    }

    public void setDumpBytes(int dumpBytes) {
        this.dumpBytes = dumpBytes;
    }

    public void setTlvBeanEncoder(TLVEncoderOfBean tlvBeanEncoder) {
        this.tlvBeanEncoder = tlvBeanEncoder;
    }
}
